// Package storage provides storage backend interfaces and implementations
package storage

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"syscall"
	"time"

	"github.com/smart-snapshotter/pkg/types"
)

// Backend represents a storage backend interface
type Backend interface {
	// File operations
	StoreFile(ctx context.Context, hash string, data []byte) error
	LoadFile(ctx context.Context, hash string) ([]byte, error)
	DeleteFile(ctx context.Context, hash string) error
	FileExists(ctx context.Context, hash string) (bool, error)

	// Block operations
	StoreBlock(ctx context.Context, hash string, data []byte) error
	LoadBlock(ctx context.Context, hash string) ([]byte, error)
	DeleteBlock(ctx context.Context, hash string) error
	BlockExists(ctx context.Context, hash string) (bool, error)

	// Metadata operations
	StoreMetadata(ctx context.Context, key string, metadata map[string]string) error
	LoadMetadata(ctx context.Context, key string) (map[string]string, error)
	DeleteMetadata(ctx context.Context, key string) error

	// Storage management
	GetStats(ctx context.Context) (StorageStats, error)
	Cleanup(ctx context.Context) error
	Close() error
}

// StorageStats represents storage backend statistics
type StorageStats struct {
	TotalSize      int64
	UsedSize       int64
	FreeSize       int64
	FileCount      int64
	BlockCount     int64
	MetadataCount  int64
	CompressionRatio float64
}

// Config represents storage backend configuration
type Config struct {
	Type           string
	BasePath       string
	MaxSize        int64
	Compression    bool
	Encryption     bool
	BlockSize      int32
	MountOptions   []string
	OverlayOptions []string
	UseOverlay     bool
}

// NewBackend creates a new storage backend based on configuration
func NewBackend(config Config) (Backend, error) {
	switch config.Type {
	case "erofs":
		return NewFileStorage(config)
	case "composefs":
		return NewFileStorage(config)
	case "overlay":
		return NewFileStorage(config)
	case "sqlite":
		return NewFileStorage(config)
	case "boltdb":
		return NewFileStorage(config)
	default:
		return nil, fmt.Errorf("unsupported storage backend type: %s", config.Type)
	}
}

// BaseBackend provides common functionality for all storage backends
type BaseBackend struct {
	config      Config
	basePath    string
	mu          sync.RWMutex
	stats       StorageStats
	lastCleanup time.Time
	compressor  *Compressor
}

// NewBaseBackend creates a new base backend
func NewBaseBackend(config Config) *BaseBackend {
	return &BaseBackend{
		config:      config,
		basePath:    config.BasePath,
		lastCleanup: time.Now(),
	}
}

// Initialize initializes the base backend
func (b *BaseBackend) Initialize() error {
	if err := os.MkdirAll(b.basePath, 0755); err != nil {
		return fmt.Errorf("failed to create base path: %w", err)
	}

	// Create subdirectories
	dirs := []string{"files", "blocks", "metadata", "temp"}
	for _, dir := range dirs {
		path := filepath.Join(b.basePath, dir)
		if err := os.MkdirAll(path, 0755); err != nil {
			return fmt.Errorf("failed to create directory %s: %w", dir, err)
		}
	}

	return nil
}

// GetFilePath returns the file path for a given hash
func (b *BaseBackend) GetFilePath(hash string) string {
	// Use sharding to avoid too many files in one directory
	if len(hash) >= 4 {
		return filepath.Join(b.basePath, "files", hash[:2], hash[2:4], hash)
	}
	return filepath.Join(b.basePath, "files", hash)
}

// GetBlockPath returns the block path for a given hash
func (b *BaseBackend) GetBlockPath(hash string) string {
	// Use sharding to avoid too many files in one directory
	if len(hash) >= 4 {
		return filepath.Join(b.basePath, "blocks", hash[:2], hash[2:4], hash)
	}
	return filepath.Join(b.basePath, "blocks", hash)
}

// GetMetadataPath returns the metadata path for a given key
func (b *BaseBackend) GetMetadataPath(key string) string {
	return filepath.Join(b.basePath, "metadata", key+".json")
}

// UpdateStats updates storage statistics
func (b *BaseBackend) UpdateStats(deltaSize int64, fileCountDelta, blockCountDelta, metadataCountDelta int64) {
	b.mu.Lock()
	defer b.mu.Unlock()

	b.stats.UsedSize += deltaSize
	b.stats.FileCount += fileCountDelta
	b.stats.BlockCount += blockCountDelta
	b.stats.MetadataCount += metadataCountDelta
}

// FileStorage provides file-based storage operations
type FileStorage struct {
	*BaseBackend
}

// Ensure FileStorage implements Backend interface
var _ Backend = (*FileStorage)(nil)

// StoreBlock stores a block (not implemented for file storage)
func (fs *FileStorage) StoreBlock(ctx context.Context, hash string, data []byte) error {
	return fmt.Errorf("block storage not supported in file storage backend")
}

// LoadBlock loads a block (not implemented for file storage)
func (fs *FileStorage) LoadBlock(ctx context.Context, hash string) ([]byte, error) {
	return nil, fmt.Errorf("block storage not supported in file storage backend")
}

// DeleteBlock deletes a block (not implemented for file storage)
func (fs *FileStorage) DeleteBlock(ctx context.Context, hash string) error {
	return fmt.Errorf("block storage not supported in file storage backend")
}

// BlockExists checks if a block exists (not implemented for file storage)
func (fs *FileStorage) BlockExists(ctx context.Context, hash string) (bool, error) {
	return false, fmt.Errorf("block storage not supported in file storage backend")
}

// Close closes the file storage
func (fs *FileStorage) Close() error {
	return nil
}

// Health checks the health of the file storage
func (fs *FileStorage) Health(ctx context.Context) error {
	return nil
}

// StoreMetadata stores metadata (not implemented for file storage)
func (fs *FileStorage) StoreMetadata(ctx context.Context, key string, metadata map[string]string) error {
	return fmt.Errorf("metadata storage not supported in file storage backend")
}

// LoadMetadata loads metadata (not implemented for file storage)
func (fs *FileStorage) LoadMetadata(ctx context.Context, key string) (map[string]string, error) {
	return nil, fmt.Errorf("metadata storage not supported in file storage backend")
}

// DeleteMetadata deletes metadata (not implemented for file storage)
func (fs *FileStorage) DeleteMetadata(ctx context.Context, key string) error {
	return fmt.Errorf("metadata storage not supported in file storage backend")
}

// NewFileStorage creates a new file-based storage
func NewFileStorage(config Config) (*FileStorage, error) {
	base := NewBaseBackend(config)
	if err := base.Initialize(); err != nil {
		return nil, err
	}
	return &FileStorage{BaseBackend: base}, nil
}

// StoreFile stores a file with optional compression and encryption
func (fs *FileStorage) StoreFile(ctx context.Context, hash string, data []byte) error {
	filePath := fs.GetFilePath(hash)

	// Create directory if not exists
	dir := filepath.Dir(filePath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	// Process data (compression, encryption)
	processedData, err := fs.processData(data)
	if err != nil {
		return fmt.Errorf("failed to process data: %w", err)
	}

	// Write file atomically
	tempFile := filePath + ".tmp"
	if err := os.WriteFile(tempFile, processedData, 0644); err != nil {
		return fmt.Errorf("failed to write file: %w", err)
	}

	// Atomic rename
	if err := os.Rename(tempFile, filePath); err != nil {
		os.Remove(tempFile) // Clean up temp file
		return fmt.Errorf("failed to rename file: %w", err)
	}

	// Update statistics
	fs.UpdateStats(int64(len(processedData)), 1, 0, 0)

	return nil
}

// LoadFile loads a file with decompression and decryption
func (fs *FileStorage) LoadFile(ctx context.Context, hash string) ([]byte, error) {
	filePath := fs.GetFilePath(hash)

	data, err := os.ReadFile(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, types.ErrStorageNotFound
		}
		return nil, fmt.Errorf("failed to read file: %w", err)
	}

	// Unprocess data (decompression, decryption)
	unprocessedData, err := fs.unprocessData(data)
	if err != nil {
		return nil, fmt.Errorf("failed to unprocess data: %w", err)
	}

	return unprocessedData, nil
}

// DeleteFile deletes a file
func (fs *FileStorage) DeleteFile(ctx context.Context, hash string) error {
	filePath := fs.GetFilePath(hash)

	// Get file info for statistics
	info, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return types.ErrStorageNotFound
		}
		return fmt.Errorf("failed to stat file: %w", err)
	}

	// Delete file
	if err := os.Remove(filePath); err != nil {
		return fmt.Errorf("failed to delete file: %w", err)
	}

	// Update statistics
	fs.UpdateStats(-info.Size(), -1, 0, 0)

	return nil
}

// FileExists checks if a file exists
func (fs *FileStorage) FileExists(ctx context.Context, hash string) (bool, error) {
	filePath := fs.GetFilePath(hash)
	_, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, fmt.Errorf("failed to stat file: %w", err)
	}
	return true, nil
}

// BlockStorage provides block-based storage operations
type BlockStorage struct {
	*BaseBackend
}

// NewBlockStorage creates a new block-based storage
func NewBlockStorage(config Config) (*BlockStorage, error) {
	base := NewBaseBackend(config)
	if err := base.Initialize(); err != nil {
		return nil, err
	}
	return &BlockStorage{BaseBackend: base}, nil
}

// StoreBlock stores a block
func (bs *BlockStorage) StoreBlock(ctx context.Context, hash string, data []byte) error {
	blockPath := bs.GetBlockPath(hash)

	// Create directory if not exists
	dir := filepath.Dir(blockPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	// Process data (compression, encryption)
	processedData, err := bs.processData(data)
	if err != nil {
		return fmt.Errorf("failed to process data: %w", err)
	}

	// Write block atomically
	tempFile := blockPath + ".tmp"
	if err := os.WriteFile(tempFile, processedData, 0644); err != nil {
		return fmt.Errorf("failed to write block: %w", err)
	}

	// Atomic rename
	if err := os.Rename(tempFile, blockPath); err != nil {
		os.Remove(tempFile) // Clean up temp file
		return fmt.Errorf("failed to rename block: %w", err)
	}

	// Update statistics
	bs.UpdateStats(int64(len(processedData)), 0, 1, 0)

	return nil
}

// LoadBlock loads a block
func (bs *BlockStorage) LoadBlock(ctx context.Context, hash string) ([]byte, error) {
	blockPath := bs.GetBlockPath(hash)

	data, err := os.ReadFile(blockPath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, types.ErrStorageNotFound
		}
		return nil, fmt.Errorf("failed to read block: %w", err)
	}

	// Unprocess data (decompression, decryption)
	unprocessedData, err := bs.unprocessData(data)
	if err != nil {
		return nil, fmt.Errorf("failed to unprocess data: %w", err)
	}

	return unprocessedData, nil
}

// DeleteBlock deletes a block
func (bs *BlockStorage) DeleteBlock(ctx context.Context, hash string) error {
	blockPath := bs.GetBlockPath(hash)

	// Get block info for statistics
	info, err := os.Stat(blockPath)
	if err != nil {
		if os.IsNotExist(err) {
			return types.ErrStorageNotFound
		}
		return fmt.Errorf("failed to stat block: %w", err)
	}

	// Delete block
	if err := os.Remove(blockPath); err != nil {
		return fmt.Errorf("failed to delete block: %w", err)
	}

	// Update statistics
	bs.UpdateStats(-info.Size(), 0, -1, 0)

	return nil
}

// BlockExists checks if a block exists
func (bs *BlockStorage) BlockExists(ctx context.Context, hash string) (bool, error) {
	blockPath := bs.GetBlockPath(hash)
	_, err := os.Stat(blockPath)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, fmt.Errorf("failed to stat block: %w", err)
	}
	return true, nil
}

// Data processing methods

func (b *BaseBackend) processData(data []byte) ([]byte, error) {
	result := data

	// Apply compression if enabled
	if b.config.Compression {
		compressed, err := compressData(result)
		if err != nil {
			return nil, fmt.Errorf("failed to compress data: %w", err)
		}
		result = compressed
	}

	// Apply encryption if enabled
	if b.config.Encryption {
		encrypted, err := encryptData(result)
		if err != nil {
			return nil, fmt.Errorf("failed to encrypt data: %w", err)
		}
		result = encrypted
	}

	return result, nil
}

func (b *BaseBackend) unprocessData(data []byte) ([]byte, error) {
	result := data

	// Decrypt if encryption is enabled
	if b.config.Encryption {
		decrypted, err := decryptData(result)
		if err != nil {
			return nil, fmt.Errorf("failed to decrypt data: %w", err)
		}
		result = decrypted
	}

	// Decompress if compression is enabled
	if b.config.Compression {
		decompressed, err := decompressData(result)
		if err != nil {
			return nil, fmt.Errorf("failed to decompress data: %w", err)
		}
		result = decompressed
	}

	return result, nil
}

// Cleanup performs cleanup operations
func (b *BaseBackend) Cleanup(ctx context.Context) error {
	b.mu.Lock()
	defer b.mu.Unlock()

	// Clean up temporary files
	tempDir := filepath.Join(b.basePath, "temp")
	if err := os.RemoveAll(tempDir); err != nil {
		return fmt.Errorf("failed to clean temp directory: %w", err)
	}

	// Recreate temp directory
	if err := os.MkdirAll(tempDir, 0755); err != nil {
		return fmt.Errorf("failed to recreate temp directory: %w", err)
	}

	b.lastCleanup = time.Now()
	return nil
}

// GetStats returns storage statistics
func (b *BaseBackend) GetStats(ctx context.Context) (StorageStats, error) {
	b.mu.RLock()
	defer b.mu.RUnlock()

	// Calculate total and free space
	var stat syscall.Statfs_t
	if err := syscall.Statfs(b.basePath, &stat); err != nil {
		return StorageStats{}, fmt.Errorf("failed to get filesystem stats: %w", err)
	}

	totalSize := int64(stat.Blocks) * int64(stat.Bsize)
	freeSize := int64(stat.Bavail) * int64(stat.Bsize)

	return StorageStats{
		TotalSize:        totalSize,
		UsedSize:         b.stats.UsedSize,
		FreeSize:         freeSize,
		FileCount:        b.stats.FileCount,
		BlockCount:       b.stats.BlockCount,
		MetadataCount:    b.stats.MetadataCount,
		CompressionRatio: float64(b.stats.UsedSize) / float64(totalSize),
	}, nil
}

// Compression and encryption functions (placeholders)

func compressData(data []byte) ([]byte, error) {
	// For now, return data as-is (compression will be implemented later)
	// This is a placeholder implementation
	return data, nil
}

func decompressData(data []byte) ([]byte, error) {
	// For now, return data as-is (decompression will be implemented later)
	// This is a placeholder implementation
	return data, nil
}

func encryptData(data []byte) ([]byte, error) {
	// TODO: Implement encryption
	return data, nil
}

func decryptData(data []byte) ([]byte, error) {
	// TODO: Implement decryption
	return data, nil
}